/*
  author Sylvain Bertrand <sylvain.bertrand@gmail.com>
  Protected by linux GNU GPLv2
  Copyright 2012-2014
*/
#include <linux/pci.h>
#include <linux/cdev.h>
#include <asm/unaligned.h>

#include <alga/rng_mng.h>
#include <uapi/alga/pixel_fmts.h>
#include <alga/timing.h>
#include <alga/amd/atombios/atb.h>
#include <uapi/alga/amd/dce6/dce6.h>
#include <alga/amd/atombios/vm.h>
#include <alga/amd/atombios/cm.h>
#include <alga/amd/atombios/pp.h>
#include <alga/amd/atombios/vram_info.h>

#include "../mc.h"
#include "../rlc.h"
#include "../ih.h"
#include "../fence.h"
#include "../ring.h"
#include "../dmas.h"
#include "../ba.h"
#include "../cps.h"
#include "../gpu.h"
#include "../drv.h"

#include "../smc_tbls.h"

#include "../regs.h"

#include "ctx.h"
#include "private.h"

#ifdef CONFIG_ALGA_AMD_SI_DYN_PM_LOG
#define L(fmt,...) printk(KERN_INFO fmt "\n", ##__VA_ARGS__)
void smc_dte_cfg_tbl_dump(struct smc_dte_cfg_tbl *tbl)
{
	u32 tmp;
	u8 i;

	L("SMC_DTE_CFG_TBL START");

	for (i = 0; i < SMC_DTE_CFG_TBL_FILTER_STAGES_N_MAX; ++i) {
		tmp = get_unaligned_be32(&tbl->tau[i]);
		L("tau[%u]=0x%08x",i,tmp);
	}

	for (i = 0; i < SMC_DTE_CFG_TBL_FILTER_STAGES_N_MAX; ++i) {
		tmp = get_unaligned_be32(&tbl->r[i]);
		L("r[%u]=0x%08x",i,tmp);
	}

	tmp = get_unaligned_be32(&tbl->k);
	L("k=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->t0);
	L("t0=0x%08x",tmp);

	tmp = get_unaligned_be32(&tbl->max_t);
	L("max_t=0x%08x",tmp);

	L("wnd_sz=0x%02x",tbl->wnd_sz);
	L("tdep_cnt=0x%02x",tbl->tdep_cnt);
	L("temp_select=0x%02x",tbl->temp_select);
	L("dte_mode=0x%02x",tbl->dte_mode);

	for (i = 0; i < SMC_DTE_CFG_TBL_MAX_TEMP_DEPENDENT_ENTRIES_N_MAX; ++i)
		L("t_limits[%u]=0x%02x",i,tbl->t_limits[i]);

	for (i = 0; i < SMC_DTE_CFG_TBL_MAX_TEMP_DEPENDENT_ENTRIES_N_MAX; ++i) {	
		tmp = get_unaligned_be32(&tbl->tdep_tau[i]);
		L("tdep_tau[%u]=0x%08x",i,tmp);
	}

	for (i = 0; i <SMC_DTE_CFG_TBL_MAX_TEMP_DEPENDENT_ENTRIES_N_MAX; ++i) {	
		tmp = get_unaligned_be32(&tbl->tdep_r[i]);
		L("tdep_r[%u]=0x%08x",i,tmp);
	}

	tmp = get_unaligned_be32(&tbl->t_threshold);
	L("t_threshold=0x%08x",tmp);

	L("SMC_DTE_CFG_TBL END");	
}
#endif

static struct smc_dte_cfg_tbl tahiti = {
	{
		1159409,
		0,
		0,
		0,
		0
	},
	{
		777,
		0,
		0,
		0,
		0
	},
	2,
	54000,
	127000,
	25,
	13,
	2,
	10,
	{
		27,
		31,
		35,
		39,
		43,
		47,
		54,
		61,
		67,
		74,
		81,
		88,
		95,
		0,
		0,
		0
	},
	{
		240888759,
		221057860,
		235370597,
		162287531,
		158510299,
		131423027,
		116673180,
		103067515,
		87941937,
		76209048,
		68209175,
		64090048,
		58301890,
		0,
		0,
		0
	},
	{
		12024,
		11189,
		11451,
		8411,
		7939,
		6666,
		5681,
		4905,
		4241,
		3720,
		3354,
		3122,
		2890,
		0,
		0,
		0
	},
	85
};

static struct smc_dte_cfg_tbl new_zealand = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0
	},
	{
		0x29b,
		0x3e9,
		0x537,
		0x7d2,
		0
	},
	0x5,
	0xafc8,
	0x69,
	0x32,
	0x10,
	1,
	0,
	{
		0x82,
		0xa0,
		0xb4,
		0xfe,
		0xfe,
		0xfe,
		0xfe,
		0xfe,
		0xfe,
		0xfe,
		0xfe,
		0xfe,
		0xfe,
		0xfe,
		0xfe,
		0xfe
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680
	},
	{
		0xdac,
		0x1388,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685,
		0x685
	},
	85
};

static struct smc_dte_cfg_tbl aruba_pro = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	5,
	45000,
	100,
	0xa,
	0x10,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680
	},
	{
		0x1000,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static struct smc_dte_cfg_tbl malta = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	5,
	45000,
	100,
	0xa,
	0x10,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680
	},
	{
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static struct smc_dte_cfg_tbl tahiti_pro = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	5,
	45000,
	100,
	0xa,
	0x10,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680
	},
	{
		0x7d0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static struct smc_dte_cfg_tbl curacao_xt = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	5,
	45000,
	100,
	0xa,
	0x10,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680
	},
	{
		0x1d17,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static struct smc_dte_cfg_tbl curacao_pro = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	5,
	45000,
	100,
	0xa,
	0x10,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680
	},
	{
		0x1d17,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static struct smc_dte_cfg_tbl neptune_xt = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	5,
	45000,
	100,
	0xa,
	0x10,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680
	},
	{
		0x3a2f,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static struct smc_dte_cfg_tbl null = {
	{ 0, 0, 0, 0, 0 },
	{ 0, 0, 0, 0, 0 },
	0,
	0,
	0,
	0,
	0,
	0,
	0,
	{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
	0
};

static struct smc_dte_cfg_tbl venus_xtx = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0x71c,
		0xaab,
		0xe39,
		0x11c7,
		0x0
	},
	5,
	55000,
	0x69,
	0xa,
	0x3,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	{
		0xd6d8,
		0x88b8,
		0x1555,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static struct smc_dte_cfg_tbl venus_xt = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0xbda,
		0x11c7,
		0x17b4,
		0x1da1,
		0x0
	},
	5,
	55000,
	0x69,
	0xa,
	0x3,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	{
		0xafc8,
		0x88b8,
		0x238e,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static struct smc_dte_cfg_tbl venus_pro = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0x11c7,
		0x1aab,
		0x238e,
		0x2c72,
		0x0
	},
	5,
	55000,
	0x69,
	0xa,
	0x3,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	{
		0x88b8,
		0x88b8,
		0x3555,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static struct smc_dte_cfg_tbl mars_pro = {
	{
		0x1e8480,
		0x3d0900,
		0x989680,
		0x2625a00,
		0x0
	},
	{
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	5,
	55000,
	105,
	0xa,
	0x10,
	1,
	0,
	{
		0x96,
		0xb4,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff,
		0xff
	},
	{
		0x895440,
		0x3d0900,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680,
		0x989680
	},
	{
		0xf627,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0,
		0x0
	},
	90
};

static void patch_from_pl2(struct ctx *ctx, struct smc_dte_cfg_tbl *dte)
{
	u32 p_limit_0;
	u32 p_limit_1;
	u32 k;
	u32 t_max;
	u32 t_split[5] = { 10, 15, 20, 25, 30 };
	u32 t_0;
	u32 i;

	p_limit_0 = ctx->atb_tdp_limit;
	p_limit_1 = ctx->atb_near_tdp_limit;
	k = get_unaligned_be32(&dte->k);
	t_max = get_unaligned_be32(&dte->max_t);
	t_0 = get_unaligned_be32(&dte->t0);

	if (p_limit_1 == 0 || p_limit_1 > p_limit_0) {
		dev_warn(&ctx->dev->dev, "dynpm:smc digital temperature estimation table cannot be patched due to invalid thermal design power (tdp) limits, ignoring\n");
		return;
	}

	dte->tdep_cnt = 3;

	for (i = 0; i < k; ++i)
		put_unaligned_be32((t_split[i] * (t_max - t_0/(u32)1000)
			* (1 << 14)) / (p_limit_1  * (u32)100), &dte->r[i]);

	put_unaligned_be32(get_unaligned_be32(&dte->r[4]) * 2, &dte->tdep_r[1]);

	for (i = 2; i < SMC_DTE_CFG_TBL_MAX_TEMP_DEPENDENT_ENTRIES_N_MAX; ++i)
		dte->tdep_r[i] = dte->r[4]; /* be32 to be32 */
}

static void cpu_to_be(struct smc_dte_cfg_tbl *dte)
{
	u8 i;

	for (i = 0; i < SMC_DTE_CFG_TBL_FILTER_STAGES_N_MAX; ++i)
		put_unaligned_be32((u32)(dte->tau[i]), &dte->tau[i]);

	for (i = 0; i < SMC_DTE_CFG_TBL_FILTER_STAGES_N_MAX; ++i)
		put_unaligned_be32((u32)(dte->r[i]), &dte->r[i]);

	put_unaligned_be32((u32)(dte->k), &dte->k);
	put_unaligned_be32((u32)(dte->t0), &dte->t0);
	put_unaligned_be32((u32)(dte->max_t), &dte->max_t);

	for (i = 0; i < SMC_DTE_CFG_TBL_MAX_TEMP_DEPENDENT_ENTRIES_N_MAX; ++i)
		put_unaligned_be32((u32)(dte->tdep_tau[i]), &dte->tdep_tau[i]);

	for (i = 0; i < SMC_DTE_CFG_TBL_MAX_TEMP_DEPENDENT_ENTRIES_N_MAX; ++i)
		put_unaligned_be32((u32)(dte->tdep_r[i]), &dte->tdep_r[i]);
	
	put_unaligned_be32((u32)(dte->t_threshold), &dte->t_threshold);
}

void smc_dte_cfg_tbl_init(struct ctx *ctx, struct smc_dte_cfg_tbl *dte)
{
	struct dev_drv_data *dd;
	u8 need_patch_from_pl2;

	LOG("smc digital temperature estimation table init");

	dd = pci_get_drvdata(ctx->dev);
	need_patch_from_pl2 = 0;

	switch (dd->family) {
	case TAHITI:
		switch (ctx->dev->device) {
		case 0x6799:
			*dte = new_zealand;
			break;
		case 0x6790:
		case 0x6791:
		case 0x6792:
		case 0x679e:
			*dte = aruba_pro;
			need_patch_from_pl2 = 1;
			break;
		case 0x679b:
			*dte = malta;
			need_patch_from_pl2 = 1;
			break;
		case 0x679a:
			*dte = tahiti_pro;
			need_patch_from_pl2 = 1;
			break;
		default:
			*dte = tahiti;
			break;
		};
		break;
	case PITCAIRN:
		switch (ctx->dev->device) {
		case 0x6810:
		case 0x6818:
			*dte = curacao_xt;
			need_patch_from_pl2 = 1;
			break;
		case 0x6819:
		case 0x6811:
			*dte = curacao_pro;
			need_patch_from_pl2 = 1;
			break;
		case 0x6800:
		case 0x6806:
			*dte = neptune_xt;
			need_patch_from_pl2 = 1;
			break;
		default:
			*dte = null;
			break;
		};
		break;
	case VERDE:
		switch (ctx->dev->device) {
		case 0x6820:
			*dte = venus_xtx;
			break;
		case 0x6821:
			*dte = venus_xt;
			break;
		case 0x6823:
		case 0x682b:
			*dte = venus_pro;
			break;
		default:
			*dte = null;
			break;
		};
		break;
	case OLAND:
		switch (ctx->dev->device) {
		case 0x6601:
		case 0x6621:
		case 0x6603:
		case 0x6600:
		case 0x6606:
		case 0x6620:
		case 0x6611:
		case 0x6610:
			*dte = mars_pro;
			need_patch_from_pl2 = 1;
			break;
		default:
			*dte = null;
			break;
		};
		break;
	}

	cpu_to_be(dte);

	if (need_patch_from_pl2)
		patch_from_pl2(ctx, dte);
}
